//******************************************************************
//******************************************************************
//**********          ANts Peer To Peer Sources        *************
//
// ANts P2P realizes a third generation P2P net. It protects your
// privacy while you are connected and makes you not trackable, hiding
// your identity (ip) and crypting everything you are sending/receiving
// from others.

// Copyright (C) 2004  Roberto Rossi

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

package ants.p2p.filesharing;

import ants.p2p.*;
import ants.p2p.gui.*;
import ants.p2p.query.*;
import ants.p2p.query.security.*;
import ants.p2p.utils.indexer.BackgroundEngine;
import ants.p2p.security.*;
import ants.p2p.messages.*;

import java.beans.*;
import java.util.*;

import org.apache.log4j.*;

public class AutoresumeEngine extends Thread implements PropertyChangeListener{
  MultipleSourcesDownloadManager msdm;
  InterruptedDownload interruptedDownload;
  WarriorAnt n;
  DownloadAntPanel dap;
  ArrayList currentSupernodeSet = new ArrayList();

  boolean terminate = false;

  public PropertyChangeSupport pcs = new PropertyChangeSupport(this);

  QueryMessage currentQuery = null;
  QueryMessage currentSupernodeQueries[] = new QueryMessage[0];
  java.util.List secureConnectionRequired = new ArrayList()/*Collections.synchronizedList(new ArrayList())*/;;

  static int secureConnectionToTrace = 1000;
  public int sleepRate = 180000 + (int)((System.currentTimeMillis() % 30)*1000);

  static Logger _logger = Logger.getLogger(AutoresumeEngine.class.getName());

  public AutoresumeEngine(MultipleSourcesDownloadManager msdm, DownloadAntPanel dap) {
    this.msdm = msdm;
    this.n = msdm.n;
    this.dap = dap;
    this.interruptedDownload = null;
    this.n.propertyChangeSupport.addPropertyChangeListener(this);
    this.pcs.addPropertyChangeListener(dap);
    this.setMultipleSourcesDownloadManager(msdm);
    this.setPriority(1);
  }

  public AutoresumeEngine(InterruptedDownload interruptedDownload, WarriorAnt n, DownloadAntPanel dap) {
    this.msdm = null;
    this.interruptedDownload = interruptedDownload;
    this.n = n;
    this.dap = dap;
    this.n.propertyChangeSupport.addPropertyChangeListener(this);
    this.pcs.addPropertyChangeListener(dap);
    this.setInterruptedDownload(interruptedDownload);
    this.setPriority(1);
  }

  public void deactivate(){
    if(this.n != null)
      this.n.propertyChangeSupport.removePropertyChangeListener(this);
    this.terminate = true;
    if(this.msdm != null){
      _logger.info("Autoresume Engine deactivated for file: " + this.msdm.getFileHash());
      this.msdm = null;
    }else if(this.interruptedDownload != null){
      _logger.info("Autoresume Engine deactivated for file: " + this.interruptedDownload.getFileHash());
      this.interruptedDownload = null;
    }
  }

  public boolean deactivated(){
    return this.terminate;
  }

  public void run(){
    try{
      Thread.sleep((System.currentTimeMillis() % 60)*1000);
      while(!n.isDisconnected() && !terminate){
        synchronized(this){
          if (this.interruptedDownload != null) {
            if(this.interruptedDownload.ae != this) terminate = true;
            if(DownloadAntPanel.maxConcurrentDownloads == 0 ||
               this.dap.getActiveDownloadsNumber() < DownloadAntPanel.maxConcurrentDownloads){
              _logger.info(this + "Autoresume is running for file (interrupted): " +
                           this.interruptedDownload.
                           getFileHash());
              QueryHashItem qsi = new QueryHashItem(null,
                  this.interruptedDownload.
                  getFileHash(), QueryHashItem.ANTS_HASH, this.interruptedDownload.getChunkHashes() == null ? true : false);
              AsymmetricProvider ap = new AsymmetricProvider(false);
              //local
              QueryManager queryManager = new QueryManager(qsi, this.n);
              ArrayList resultSet = queryManager.resultSet;
              if (resultSet.size() > 0) {
                this.currentSupernodeQueries = new QueryMessage[1];
                this.currentSupernodeQueries[0] = new QueryMessage(qsi, ap.getPublicHeader());
                _logger.info("Analyzing local results...");
                PropertyChangeEvent pce = new PropertyChangeEvent(this,
                    "queryCompleted", this.currentSupernodeQueries[0],
                    queryManager.resultSet);
                this.processCurrentEvent(pce);
                _logger.info("Ricevuta parte di query file");
              }
              //remote
              if(BackgroundEngine.getInstance().getSupernodeList().size() > 0){
                java.util.List supernodesSubset = new ArrayList();
                this.currentSupernodeSet.retainAll(new ArrayList(BackgroundEngine.getInstance().getSupernodeList().keySet()));
                if(this.currentSupernodeSet.size() > 0){
                  supernodesSubset = new ArrayList(this.currentSupernodeSet.subList(0, (int)Math.min(5, this.currentSupernodeSet.size())));
                  this.currentSupernodeSet.removeAll(supernodesSubset);
                }else{
                  this.currentSupernodeSet = new ArrayList(BackgroundEngine.getInstance().getSupernodeList().keySet());
                  Collections.shuffle(this.currentSupernodeSet);
                  supernodesSubset = new ArrayList(this.currentSupernodeSet.subList(0, (int)Math.min(5, this.currentSupernodeSet.size())));
                  this.currentSupernodeSet.remove(supernodesSubset);
                }
                MessageWrapper[] queryWrappers = this.n.doSupernodeQuery(qsi, ap.getPublicHeader(), supernodesSubset);
                this.currentSupernodeQueries = new QueryMessage[queryWrappers.length];
                for(int x = 0; x < queryWrappers.length; x++){
                  if(queryWrappers[x].getMessage() != null && queryWrappers[x].getMessage() instanceof QueryMessage){
                    this.currentSupernodeQueries[x] = (QueryMessage) queryWrappers[x].getMessage();
                  }
                }
              }else{
                Message query = this.n.doQuery(
                    qsi, ap.getPublicHeader()).
                    getMessage();
                if (query != null && query instanceof QueryMessage)
                  this.currentQuery = (QueryMessage) query;
              }
            }
          }
          else if (this.msdm != null) {
            if(this.msdm.ae != this) terminate = true;
            if(this.msdm.getPeersNumber() < MultipleSourcesDownloadManager.MaxSources){
              _logger.info(this + "Autoresume is running for file (downloading): " +
                           this.msdm.
                           getFileHash());
              QueryHashItem qsi = new QueryHashItem(null,
                  this.msdm.
                  getFileHash(), QueryHashItem.ANTS_HASH, this.msdm.getChunkHashes() == null ? true : false);
              AsymmetricProvider ap = new AsymmetricProvider(false);
              //local
              QueryManager queryManager = new QueryManager(qsi, this.n);
              ArrayList resultSet = queryManager.resultSet;
              if (resultSet.size() > 0) {
                this.currentSupernodeQueries = new QueryMessage[1];
                this.currentSupernodeQueries[0] = new QueryMessage(qsi, ap.getPublicHeader());
                _logger.info("Analyzing local results...");
                PropertyChangeEvent pce = new PropertyChangeEvent(this,
                    "queryCompleted", this.currentSupernodeQueries[0],
                    queryManager.resultSet);
                this.processCurrentEvent(pce);
                _logger.info("Ricevuta parte di query file");
              }
              //remote
              if(BackgroundEngine.getInstance().getSupernodeList().size() > 0){
                java.util.List supernodesSubset = new ArrayList();
                this.currentSupernodeSet.retainAll(new ArrayList(BackgroundEngine.getInstance().getSupernodeList().keySet()));
                if(this.currentSupernodeSet.size() > 0){
                  supernodesSubset = new ArrayList(this.currentSupernodeSet.subList(0, (int)Math.min(5, this.currentSupernodeSet.size())));
                  this.currentSupernodeSet.removeAll(supernodesSubset);
                }else{
                  this.currentSupernodeSet = new ArrayList(BackgroundEngine.getInstance().getSupernodeList().keySet());
                  Collections.shuffle(this.currentSupernodeSet);
                  supernodesSubset = new ArrayList(this.currentSupernodeSet.subList(0, (int)Math.min(5, this.currentSupernodeSet.size())));
                  this.currentSupernodeSet.removeAll(supernodesSubset);
                }
                MessageWrapper[] queryWrappers = this.n.doSupernodeQuery(qsi, ap.getPublicHeader(), supernodesSubset);
                this.currentSupernodeQueries = new QueryMessage[queryWrappers.length];
                for(int x = 0; x < queryWrappers.length; x++){
                  if(queryWrappers[x].getMessage() != null && queryWrappers[x].getMessage() instanceof QueryMessage){
                    this.currentSupernodeQueries[x] = (QueryMessage) queryWrappers[x].getMessage();
                  }
                }
              }else{
                Message query = this.n.doQuery(
                    qsi, ap.getPublicHeader()).
                    getMessage();
                if (query != null && query instanceof QueryMessage)
                  this.currentQuery = (QueryMessage) query;
              }
            }
          }else{
            this.terminate = true;
          }
        }
        this.sleep(sleepRate);
      }
    }catch(Exception e){
      _logger.error("Autoresume Engine Exception",e);
    }
    if (this.interruptedDownload != null) {
      this.interruptedDownload.ae = null;
    }else if (this.msdm != null) {
      this.msdm.ae = null;
    }
    this.n.propertyChangeSupport.removePropertyChangeListener(this);
    this.n = null;
    this.terminate = true;
    _logger.info(this +" AutoresumeEngine DEACTIVATED");
  }

  private boolean isMySupernodeQuery(QueryMessage query){
    for(int x = 0; x < this.currentSupernodeQueries.length; x++){
      if(this.currentSupernodeQueries[x] != null &&
         this.currentSupernodeQueries[x].getLocalQueryId() == query.getLocalQueryId())
        return true;
    }
    return false;
  }

  private void processCurrentEvent(PropertyChangeEvent e){
    if (e.getPropertyName().equals("queryCompleted")) {
      QueryMessage eventQuerySource = (QueryMessage) e.getOldValue();
      if ((this.currentQuery != null && eventQuerySource.getLocalQueryId() == currentQuery.getLocalQueryId()) ||
          this.isMySupernodeQuery(eventQuerySource)) {
        ArrayList resultset = this.mergeQueryResults( (ArrayList) e.
            getNewValue());
        for (int x = 0;
             x < resultset.size() &&
             (msdm == null || msdm.getPeersNumber() < msdm.MaxSources); x++) {
          FileTupleGroup ftg = (FileTupleGroup) resultset.get(x);
          _logger.info("Autoresume query completed for file: " +
                       ftg.getHash());
          Object[] tuples = ftg.tuples.toArray();
          Arrays.sort(tuples, new QueryFileTupleComparator());
          for (int y = 0;
               y < tuples.length &&
               (msdm == null || msdm.getPeersNumber() < msdm.MaxSources); y++) {
            QueryFileTuple qft = (QueryFileTuple) tuples[y];
            EndpointSecurityManager esm = this.n.getOutputSecureConnectionManager(qft.getOwnerID());
            if (esm != null) {
              _logger.info("Secure connection " + esm + " adding peer " + qft.getOwnerID().substring(0,10));
              this.pcs.firePropertyChange("addSourcePeer", this, qft);
            }
            else {
              _logger.info("Secure connection " + esm + "... creating " + qft.getOwnerID().substring(0,10));
              this.n.createSecureConnection(qft.getOwnerID(), false);//!(qft instanceof QueryRemoteFileTuple));
              synchronized (this.secureConnectionRequired) {
                if(!this.secureConnectionRequired.contains(qft)){
                  if (this.secureConnectionRequired.size() > secureConnectionToTrace)
                    this.secureConnectionRequired.remove(0);
                  this.secureConnectionRequired.add(qft);
                }
              }
            }
          }
        }
      }
    }
    if(e.getPropertyName().equals("secureConnectionCreated")){
      synchronized(this.secureConnectionRequired){
        for (int x = 0; x < this.secureConnectionRequired.size() &&
             (msdm == null || msdm.getPeersNumber() < msdm.MaxSources); x++) {
          QueryFileTuple qft = (QueryFileTuple)this.secureConnectionRequired.get(x);
          if (qft.getOwnerID().equals( ( (SecurityResponseMessage) e.getNewValue()).getSource())) {
            _logger.info("Autoresume secure connection created file: " + qft.getFileHash());
            this.secureConnectionRequired.remove(x);
            this.pcs.firePropertyChange("addSourcePeer", this, qft);
            return;
          }
        }
      }
    }
  }

  public void propertyChange(PropertyChangeEvent e) {
    final PropertyChangeEvent event = e;
    Thread processor = new Thread(){
      public void run(){
        processCurrentEvent(event);
      }
    };
    processor.setPriority(1);
    processor.start();
  }

  public ArrayList mergeQueryResults(ArrayList resultSet){
    ArrayList tupleGroups = new ArrayList();
    for(int x=0; x < resultSet.size(); x++){
      if(resultSet.get(x) instanceof QueryCompletedFileTuple){
        if(tupleGroups.contains((QueryCompletedFileTuple)resultSet.get(x))){
          FileTupleGroup ftg = (FileTupleGroup)tupleGroups.get(tupleGroups.indexOf((QueryCompletedFileTuple)resultSet.get(x)));
          if(!ftg.tuples.contains(resultSet.get(x))){
            ftg.tuples.add(resultSet.get(x));
          }
          else{
            ftg.tuples.remove(resultSet.get(x));
            ftg.tuples.add(resultSet.get(x));
          }
        }else{
          QueryCompletedFileTuple qft = (QueryCompletedFileTuple)resultSet.get(x);
          FileTupleGroup ftg = new FileTupleGroup(qft.getFileHash(),qft.getSize());
          ftg.tuples.add(qft);
          tupleGroups.add(ftg);
        }
      }else if(resultSet.get(x) instanceof QueryPartialFileTuple){
        if(tupleGroups.contains((QueryPartialFileTuple)resultSet.get(x))){
          FileTupleGroup ftg = (FileTupleGroup)tupleGroups.get(tupleGroups.indexOf((QueryPartialFileTuple)resultSet.get(x)));
          if(!ftg.tuples.contains(resultSet.get(x))){
            ftg.tuples.add(resultSet.get(x));
          }
          else{
            ftg.tuples.remove(resultSet.get(x));
            ftg.tuples.add(resultSet.get(x));
          }
        }else{
          QueryPartialFileTuple qpft = (QueryPartialFileTuple)resultSet.get(x);
          FileTupleGroup ftg = new FileTupleGroup(qpft.getFileHash(),qpft.getSize());
          ftg.tuples.add(qpft);
          tupleGroups.add(ftg);
        }
      }else if(resultSet.get(x) instanceof QueryRemoteFileTuple){
        if(!tupleGroups.contains((QueryRemoteFileTuple)resultSet.get(x)) &&
           !this.n.getIdent().equals(((QueryRemoteFileTuple)resultSet.get(x)).getOwnerID())){
          QueryRemoteFileTuple qrft = (QueryRemoteFileTuple)resultSet.get(x);
          FileTupleGroup ftg = new FileTupleGroup(qrft.getFileHash(),qrft.getSize());
          ftg.tuples.add(qrft);
          tupleGroups.add(ftg);
        }else if(!this.n.getIdent().equals(((QueryRemoteFileTuple)resultSet.get(x)).getOwnerID())){
          QueryRemoteFileTuple qrft = (QueryRemoteFileTuple)resultSet.get(x);
          FileTupleGroup ftg = (FileTupleGroup)tupleGroups.get(tupleGroups.indexOf(qrft));
          if(ftg.tuples.contains(resultSet.get(x)) && ftg.tuples.get(ftg.tuples.indexOf(resultSet.get(x))) instanceof QueryRemoteFileTuple){
            QueryRemoteFileTuple oldQrft = (QueryRemoteFileTuple)ftg.tuples.get(ftg.tuples.indexOf(resultSet.get(x)));
            if(oldQrft.getLastTimeSeen().longValue() < qrft.getLastTimeSeen().longValue()){
              ftg.tuples.set(ftg.tuples.indexOf(resultSet.get(x)),qrft);
            }
          }
          else if(!ftg.tuples.contains(resultSet.get(x))){
            ftg.tuples.add(qrft);
          }
        }
      }
    }
    return tupleGroups;
  }

  public void setInterruptedDownload(InterruptedDownload interruptedDownload){
    //synchronized(this){
      this.interruptedDownload = interruptedDownload;
      if(this.msdm != null) this.msdm.ae = null;
      this.msdm = null;
    //}
    this.interruptedDownload.addAutoresumeEngine(this);
  }

  public void setMultipleSourcesDownloadManager(MultipleSourcesDownloadManager msdm){
    //synchronized(this){
      this.msdm = msdm;
      if(this.interruptedDownload != null) this.interruptedDownload.ae = null;
      this.interruptedDownload = null;
    //}
    this.msdm.addAutoresumeEngine(this);
  }
}
